﻿using System;
using System.Collections.Generic;
using System.Text;
using Tao.OpenGl;
using PacMan3D.Windows;
using PacMan3D.Utils;
using PacMan3D.Camera;
using PacMan3D.IO;
using PacMan3D.Objects;

namespace PacMan3D.Camera
{
    public class CameraViewer
    {
        private Maze Maze;

        private Observer GodObserver;
        private Observer PacManObserver;
        public CameraObserver CurrentObserver;

        private float GodAngle = 45.0f, PacManAngle = 90.0f, zNear = 0.1f, zFar;

        public CameraViewer(Maze maze) 
        {
            this.Maze = maze;

            this.zFar = 2 * 100 * this.Maze.BlockSize;

            this.GodObserver = new Observer();
            this.PacManObserver = new Observer();

            this.CurrentObserver = CameraObserver.God;
            this.Maze.PacMan.ShowPacMan();
        }

        private float angle;
        public void Film()
        {
            UpdateObservers();

            Gl.glClear(Gl.GL_COLOR_BUFFER_BIT | Gl.GL_DEPTH_BUFFER_BIT);

            Gl.glViewport(0, 0, Window.Instance().Width, Window.Instance().Height);

            this.angle = ((this.CurrentObserver == CameraObserver.PacMan) ? this.PacManAngle : this.GodAngle);

            Gl.glMatrixMode(Gl.GL_PROJECTION);
            Gl.glLoadIdentity();
            Glu.gluPerspective(angle, Window.Instance().Width / Window.Instance().Height, zNear, zFar);

            Gl.glMatrixMode(Gl.GL_MODELVIEW);
            Gl.glLoadIdentity();

            // coloca o observador ...
            this.GetCurrentObserver().Watch();
        }

        public bool AlowedEnableFog()
        {
            return (this.CurrentObserver == CameraObserver.PacMan);
        }

        /// <summary>
        /// Método que troca o observador actual.
        /// </summary>
        public void ChangeCurrentObserver()
        {
            if (this.CurrentObserver == CameraObserver.God)
            {
                this.CurrentObserver = CameraObserver.PacMan;
                this.Maze.PacMan.HidePacMan();
            }
            else
            {
                this.CurrentObserver = CameraObserver.God;
                this.Maze.PacMan.ShowPacMan();
            }
        }

        public Observer GetCurrentObserver()
        {
            return ((this.CurrentObserver == CameraObserver.God) ? this.GodObserver : this.PacManObserver);
        }

        private const float distY = 15.0f;
        private const float distX = 10.0f;
        private void UpdateObservers()
        {
            // Update God Observer ... 
            this.GodObserver.Position.X = this.Maze.PacMan.Position.X - distX - (0.10f * this.Maze.PacMan.Position.X);
            this.GodObserver.Position.Y = distY + this.Maze.PacMan.Position.X * 0.25f;
            this.GodObserver.Position.Z = this.Maze.PacMan.Position.Z;
            this.GodObserver.Center = this.Maze.PacMan.Position;

            // Update PacMan Observer ...
            this.PacManObserver.Position.X = this.Maze.PacMan.Position.X;
            this.PacManObserver.Position.Y = this.Maze.PacMan.Position.Y + this.Maze.PacMan.Size;
            this.PacManObserver.Position.Z = this.Maze.PacMan.Position.Z;

            this.PacManObserver.Center.X = this.Maze.PacMan.Position.X + this.Maze.PacMan.Direction.X * 2.0f;
            this.PacManObserver.Center.Y = this.Maze.PacMan.Position.Y + this.Maze.PacMan.Size;
            this.PacManObserver.Center.Z = this.Maze.PacMan.Position.Z + this.Maze.PacMan.Direction.Z * 2.0f;
        }

        /// <summary>
        /// Método que calcula a direção para a qual o PacMan se deve mover,
        /// tendo em consideração o observador (PacMan ou God) e a sua posição.
        /// </summary>
        /// <param name="direction"></param>
        /// <param name="maze"></param>
        public DirectionType GetRealDirection(DirectionType direction)
        {
            // Quando o observador actual é o PacMan ...
            if (this.CurrentObserver == CameraObserver.PacMan)
                return (DirectionType)((((float)direction) + 90.0f + (float)this.Maze.PacMan.DirectionType) % 360.0f);
            else
                return (DirectionType)((360 + (float)direction - 90.0f) % 360.0f);
        }

        #region Métodos que permitem alterar a posição do Observador Extermo
        private float Increment = 1.0f;
        public void DecreaseGodPositionX()
        {
            if (this.CurrentObserver == CameraObserver.PacMan)
                return ;

            if (Math.Abs(this.GodObserver.GetFinalPosition().X - Increment) >= (this.zFar / 4.0f))
                return;

            this.GodObserver.DecreaseX(Increment);
        }
        public void IncreaseGodPositionX()
        {
            if (this.CurrentObserver == CameraObserver.PacMan)
                return;

            if ((this.GodObserver.GetFinalPosition().X + Increment) >= this.Maze.PacMan.Position.X)
                return;

            this.GodObserver.IncreaseX(Increment);
        }
        public void DecreaseGodPositionY()
        {
            if (this.CurrentObserver == CameraObserver.PacMan)
                return;

            if (Math.Abs(this.GodObserver.GetFinalPosition().Y - Increment) >= (this.zFar / 4.0f))
                return;

            this.GodObserver.DecreaseY(Increment);
        }

        public void IncreaseGodPositionY()
        {
            if (this.CurrentObserver == CameraObserver.PacMan)
                return;

            if (Math.Abs(this.GodObserver.GetFinalPosition().Y + Increment) >= (this.zFar / 4.0f))
                return;

            this.GodObserver.IncreaseY(Increment);
        }
        #endregion
    }

    public enum CameraObserver { PacMan, God }
}
